{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "07b3a819",
   "metadata": {},
   "source": [
    "# OrangePi AIpro 中文文本分类 (MindSpore + MindSpore NLP)\n",
    "\n",
    "## 组件版本要求\n",
    "- Python 3.9\n",
    "- CANN Toolkit/Kernels 8.0.0.beta1\n",
    "- MindSpore Ascend 2.6.0\n",
    "- MindSpore NLP 0.4.1\n",
    "\n",
    "本 Notebook 结构：\n",
    "1. 环境检测与依赖安装检查\n",
    "2. 加载中文文本分类模型\n",
    "3. 推理示例\n",
    "4. 推理性能评估\n",
    "5. 生成独立推理脚本与运行指令"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37bc5d99",
   "metadata": {},
   "source": [
    "## 1. 环境检测与依赖确认"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "71c126dc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python 3.9.2 OK\n",
      "[OK] mindspore 已满足 -> 2.6.0\n",
      "[INFO] mindnlp 版本 ? != 期望 0.4.1, 准备重新安装\n",
      "[INSTALL] pip install mindnlp==0.4.1 -i https://pypi.tuna.tsinghua.edu.cn/simple\n",
      "Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n",
      "Requirement already satisfied: mindnlp==0.4.1 in /usr/local/miniconda3/lib/python3.9/site-packages (0.4.1)\n",
      "Requirement already satisfied: tqdm in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (4.67.1)\n",
      "Requirement already satisfied: safetensors in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (0.6.2)\n",
      "Requirement already satisfied: ml-dtypes in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (0.5.3)\n",
      "Requirement already satisfied: pytest==7.2.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (7.2.0)\n",
      "Requirement already satisfied: sentencepiece in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (0.2.1)\n",
      "Requirement already satisfied: pyctcdecode in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (0.5.0)\n",
      "Requirement already satisfied: requests in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (2.32.5)\n",
      "Requirement already satisfied: evaluate in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (0.4.5)\n",
      "Requirement already satisfied: addict in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (2.4.0)\n",
      "Requirement already satisfied: tokenizers==0.19.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (0.19.1)\n",
      "Requirement already satisfied: regex in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (2025.7.34)\n",
      "Requirement already satisfied: mindspore>=2.2.14 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (2.6.0)\n",
      "Requirement already satisfied: datasets in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (4.0.0)\n",
      "Requirement already satisfied: pillow>=10.0.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindnlp==0.4.1) (10.2.0)\n",
      "Requirement already satisfied: tomli>=1.0.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from pytest==7.2.0->mindnlp==0.4.1) (2.0.1)\n",
      "Requirement already satisfied: attrs>=19.2.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from pytest==7.2.0->mindnlp==0.4.1) (23.2.0)\n",
      "Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /usr/local/miniconda3/lib/python3.9/site-packages (from pytest==7.2.0->mindnlp==0.4.1) (1.2.0)\n",
      "Requirement already satisfied: iniconfig in /usr/local/miniconda3/lib/python3.9/site-packages (from pytest==7.2.0->mindnlp==0.4.1) (2.1.0)\n",
      "Requirement already satisfied: packaging in /usr/local/miniconda3/lib/python3.9/site-packages (from pytest==7.2.0->mindnlp==0.4.1) (23.1)\n",
      "Requirement already satisfied: pluggy<2.0,>=0.12 in /usr/local/miniconda3/lib/python3.9/site-packages (from pytest==7.2.0->mindnlp==0.4.1) (1.0.0)\n",
      "Requirement already satisfied: huggingface-hub<1.0,>=0.16.4 in /usr/local/miniconda3/lib/python3.9/site-packages (from tokenizers==0.19.1->mindnlp==0.4.1) (0.34.4)\n",
      "Requirement already satisfied: protobuf>=3.13.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindspore>=2.2.14->mindnlp==0.4.1) (3.20.0)\n",
      "Requirement already satisfied: psutil>=5.6.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindspore>=2.2.14->mindnlp==0.4.1) (5.9.8)\n",
      "Requirement already satisfied: numpy<2.0.0,>=1.20.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindspore>=2.2.14->mindnlp==0.4.1) (1.22.4)\n",
      "Requirement already satisfied: asttokens>=2.0.4 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindspore>=2.2.14->mindnlp==0.4.1) (3.0.0)\n",
      "Requirement already satisfied: scipy>=1.5.4 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindspore>=2.2.14->mindnlp==0.4.1) (1.12.0)\n",
      "Requirement already satisfied: dill>=0.3.7 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindspore>=2.2.14->mindnlp==0.4.1) (0.3.8)\n",
      "Requirement already satisfied: astunparse>=1.6.3 in /usr/local/miniconda3/lib/python3.9/site-packages (from mindspore>=2.2.14->mindnlp==0.4.1) (1.6.3)\n",
      "Requirement already satisfied: multiprocess<0.70.17 in /usr/local/miniconda3/lib/python3.9/site-packages (from datasets->mindnlp==0.4.1) (0.70.16)\n",
      "Requirement already satisfied: filelock in /usr/local/miniconda3/lib/python3.9/site-packages (from datasets->mindnlp==0.4.1) (3.13.1)\n",
      "Requirement already satisfied: xxhash in /usr/local/miniconda3/lib/python3.9/site-packages (from datasets->mindnlp==0.4.1) (3.5.0)\n",
      "Requirement already satisfied: pandas in /usr/local/miniconda3/lib/python3.9/site-packages (from datasets->mindnlp==0.4.1) (2.3.2)\n",
      "Requirement already satisfied: fsspec[http]<=2025.3.0,>=2023.1.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from datasets->mindnlp==0.4.1) (2023.12.2)\n",
      "Requirement already satisfied: pyarrow>=15.0.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from datasets->mindnlp==0.4.1) (21.0.0)\n",
      "Requirement already satisfied: pyyaml>=5.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from datasets->mindnlp==0.4.1) (6.0.1)\n",
      "Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->mindnlp==0.4.1) (2.0.4)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->mindnlp==0.4.1) (2023.11.17)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->mindnlp==0.4.1) (3.4)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->mindnlp==0.4.1) (2.5.0)\n",
      "Requirement already satisfied: hypothesis<7,>=6.14 in /usr/local/miniconda3/lib/python3.9/site-packages (from pyctcdecode->mindnlp==0.4.1) (6.138.7)\n",
      "Requirement already satisfied: pygtrie<3.0,>=2.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from pyctcdecode->mindnlp==0.4.1) (2.5.0)\n",
      "Requirement already satisfied: wheel<1.0,>=0.23.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from astunparse>=1.6.3->mindspore>=2.2.14->mindnlp==0.4.1) (0.37.1)\n",
      "Requirement already satisfied: six<2.0,>=1.6.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from astunparse>=1.6.3->mindspore>=2.2.14->mindnlp==0.4.1) (1.16.0)\n",
      "Requirement already satisfied: aiohttp!=4.0.0a0,!=4.0.0a1 in /usr/local/miniconda3/lib/python3.9/site-packages (from fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (3.12.15)\n",
      "Requirement already satisfied: hf-xet<2.0.0,>=1.1.3 in /usr/local/miniconda3/lib/python3.9/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers==0.19.1->mindnlp==0.4.1) (1.1.9)\n",
      "Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/miniconda3/lib/python3.9/site-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers==0.19.1->mindnlp==0.4.1) (4.15.0)\n",
      "Requirement already satisfied: sortedcontainers<3.0.0,>=2.1.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from hypothesis<7,>=6.14->pyctcdecode->mindnlp==0.4.1) (2.4.0)\n",
      "Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/miniconda3/lib/python3.9/site-packages (from pandas->datasets->mindnlp==0.4.1) (2.8.2)\n",
      "Requirement already satisfied: pytz>=2020.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from pandas->datasets->mindnlp==0.4.1) (2023.3.post1)\n",
      "Requirement already satisfied: tzdata>=2022.7 in /usr/local/miniconda3/lib/python3.9/site-packages (from pandas->datasets->mindnlp==0.4.1) (2025.2)\n",
      "Requirement already satisfied: propcache>=0.2.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (0.3.2)\n",
      "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (2.6.1)\n",
      "Requirement already satisfied: aiosignal>=1.4.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (1.4.0)\n",
      "Requirement already satisfied: async-timeout<6.0,>=4.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (4.0.3)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (1.7.0)\n",
      "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/miniconda3/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (1.20.1)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/miniconda3/lib/python3.9/site-packages (from aiohttp!=4.0.0a0,!=4.0.0a1->fsspec[http]<=2025.3.0,>=2023.1.0->datasets->mindnlp==0.4.1) (6.6.4)\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\n",
      "[WARNING] ME(4693:255086074413088,MainProcess):2025-09-01-01:37:15.109.098 [mindspore/context.py:1402] For 'context.set_context', the parameter 'device_target' will be deprecated and removed in a future version. Please use the api mindspore.set_device() instead.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[DONE] mindnlp -> ?\n",
      "[OK] tqdm 已满足 -> 4.67.1\n",
      "MindSpore 版本: 2.6.0\n",
      "MindNLP 导入失败: module 'mindnlp' has no attribute '__version__'\n",
      "Device target: Ascend\n",
      "\n",
      "发现 OPP 目录:\n",
      "  - /usr/local/Ascend/ascend-toolkit/latest/opp | op_info_cfg=缺失\n",
      "\n",
      "依赖检测/安装完成。\n"
     ]
    }
   ],
   "source": [
    "# 说明:\n",
    "# - 自动检查并安装所需 Python 包 (mindspore-ascend==2.6.0, mindnlp==0.4.1, tqdm)\n",
    "# - 可通过环境变量覆盖安装命令:\n",
    "#     MS_INSTALL_CMD  (默认: pip install mindspore-ascend==2.6.0 -i 清华源)\n",
    "#     MINDNLP_INSTALL_CMD (默认: pip install mindnlp==0.4.1 -i 清华源)\n",
    "# - 成功后打印版本与 Ascend 目标设备。\n",
    "\n",
    "import sys, importlib, subprocess, os, platform, json, time, pathlib\n",
    "REQ_PY = (3,9)\n",
    "if sys.version_info[:2] != REQ_PY:\n",
    "    raise SystemExit(f\"需要 Python {REQ_PY}, 当前: {sys.version_info[:2]}\")\n",
    "print(f\"Python {sys.version.split()[0]} OK\")\n",
    "\n",
    "DEFAULT_MS_CMD = \"pip install mindspore-ascend==2.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple\"\n",
    "DEFAULT_MINDNLP_CMD = \"pip install mindnlp==0.4.1 -i https://pypi.tuna.tsinghua.edu.cn/simple\"\n",
    "DEFAULT_TQDM_CMD = \"pip install tqdm -i https://pypi.tuna.tsinghua.edu.cn/simple\"\n",
    "\n",
    "MS_WHL = os.environ.get('MS_WHL')\n",
    "MINDNLP_WHL = os.environ.get('MINDNLP_WHL')\n",
    "MS_INSTALL_CMD = os.environ.get('MS_INSTALL_CMD') or (f\"pip install {MS_WHL}\" if MS_WHL else DEFAULT_MS_CMD)\n",
    "MINDNLP_INSTALL_CMD = os.environ.get('MINDNLP_INSTALL_CMD') or (f\"pip install {MINDNLP_WHL}\" if MINDNLP_WHL else DEFAULT_MINDNLP_CMD)\n",
    "\n",
    "PKGS = [\n",
    "    (\"mindspore\", \"2.6.0\", MS_INSTALL_CMD),\n",
    "    (\"mindnlp\", \"0.4.1\", MINDNLP_INSTALL_CMD),\n",
    "    (\"tqdm\", None, DEFAULT_TQDM_CMD),\n",
    "]\n",
    "\n",
    "\n",
    "def ensure_pkg(mod_name: str, want_ver: str, install_cmd: str):\n",
    "    \"\"\"确保模块存在且（若指定）版本匹配，不匹配则尝试安装。\"\"\"\n",
    "    need_install = False\n",
    "    try:\n",
    "        m = importlib.import_module(mod_name)\n",
    "        cur_ver = getattr(m, '__version__', '?')\n",
    "        if want_ver and want_ver not in cur_ver:\n",
    "            print(f\"[INFO] {mod_name} 版本 {cur_ver} != 期望 {want_ver}, 准备重新安装\")\n",
    "            need_install = True\n",
    "        else:\n",
    "            print(f\"[OK] {mod_name} 已满足 -> {cur_ver}\")\n",
    "    except Exception:\n",
    "        print(f\"[MISS] 未找到 {mod_name}, 准备安装\")\n",
    "        need_install = True\n",
    "    if need_install:\n",
    "        print(f\"[INSTALL] {install_cmd}\")\n",
    "        r = subprocess.run(install_cmd, shell=True)\n",
    "        if r.returncode != 0:\n",
    "            raise SystemExit(f\"安装 {mod_name} 失败，请检查网络或提供离线 whl\")\n",
    "        m = importlib.import_module(mod_name)\n",
    "        print(f\"[DONE] {mod_name} -> {getattr(m,'__version__','?')}\")\n",
    "\n",
    "for n, v, cmd in PKGS:\n",
    "    ensure_pkg(n, v, cmd)\n",
    "\n",
    "import mindspore as ms\n",
    "print('MindSpore 版本:', ms.__version__)\n",
    "try:\n",
    "    import mindnlp\n",
    "    print('MindNLP 版本:', mindnlp.__version__)\n",
    "except Exception as e:\n",
    "    print('MindNLP 导入失败:', e)\n",
    "\n",
    "try:\n",
    "    ms.set_context(device_target='Ascend')\n",
    "    print('Device target:', ms.get_context('device_target'))\n",
    "except Exception as e:\n",
    "    print('设置 Ascend 失败，可能当前环境无 NPU 驱动:', e)\n",
    "\n",
    "def find_opp_roots():\n",
    "    candidates = [\n",
    "        '/usr/local/Ascend/ascend-toolkit/latest/opp',\n",
    "        '/usr/local/Ascend/opp',\n",
    "        '/usr/local/ascend/ascend-toolkit/latest/opp',\n",
    "    ]\n",
    "    found = []\n",
    "    for c in candidates:\n",
    "        if os.path.isdir(c):\n",
    "            found.append(c)\n",
    "    return found\n",
    "\n",
    "opp_roots = find_opp_roots()\n",
    "if not opp_roots:\n",
    "    print('\\n[CRITICAL] 未找到 Ascend OPP 目录 (opp_kernel / op_info_cfg).')\n",
    "    print('可能原因:')\n",
    "    print('  1) CANN Toolkit 未完整安装 (缺少 ascend-toolkit 组件)')\n",
    "    print('  2) 安装后未正确挂载 /usr/local/Ascend 到容器')\n",
    "    print('  3) 权限或软链接缺失')\n",
    "    print('\\n建议操作 (任选适用):')\n",
    "    print('  a) 确认已安装 CANN 8.0.0.beta1 ascend-toolkit 与 driver 包')\n",
    "    print('  b) 重新执行 toolkit 安装: bash Ascend-cann-toolkit*.run --install')\n",
    "    print('  c) 若在容器, 使用 --volume /usr/local/Ascend:/usr/local/Ascend 挂载宿主路径')\n",
    "    print('  d) 检查权限: sudo chmod -R a+r /usr/local/Ascend')\n",
    "    print('  e) 安装后需执行环境脚本: source /usr/local/Ascend/ascend-toolkit/set_env.sh')\n",
    "    print('\\n后续 MindSpore 需要读取 op_info_cfg/*.json 以注册算子，缺失会继续报错。')\n",
    "else:\n",
    "    print('\\n发现 OPP 目录:')\n",
    "    for r in opp_roots:\n",
    "        op_cfg = os.path.join(r, 'op_info_cfg')\n",
    "        has_cfg = os.path.isdir(op_cfg)\n",
    "        print(f'  - {r} | op_info_cfg={\"OK\" if has_cfg else \"缺失\"}')\n",
    "        if has_cfg:\n",
    "            try:\n",
    "                cnt = len([f for f in os.listdir(op_cfg) if f.endswith('.json')])\n",
    "                print(f'    -> op_info_cfg 中 JSON 文件数: {cnt}')\n",
    "            except Exception as e:\n",
    "                print('    -> 统计失败:', e)\n",
    "\n",
    "print('\\n依赖检测/安装完成。')\n",
    "# ==================================================================="
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0190f28d",
   "metadata": {},
   "source": [
    "## 2. 加载中文文本分类模型\n",
    "\n",
    "本节直接加载已经在公开中文数据集上完成微调的文本分类模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cb7736dc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "尝试加载已微调模型: uer/roberta-base-finetuned-chinanews-chinese\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/miniconda3/lib/python3.9/site-packages/mindnlp/transformers/tokenization_utils_base.py:1526: FutureWarning: `clean_up_tokenization_spaces` was not set. It will be set to `True` by default. This behavior will be depracted, and will be then set to `False` by default. \n",
      "  warnings.warn(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[OK] 使用模型: uer/roberta-base-finetuned-chinanews-chinese\n",
      "标签数: 7\n"
     ]
    }
   ],
   "source": [
    "from mindnlp.transformers import AutoTokenizer, AutoModelForSequenceClassification\n",
    "\n",
    "FINETUNED_CANDIDATES = [\n",
    "    'uer/roberta-base-finetuned-chinanews-chinese',  # 中国新闻分类\n",
    "    'clue/roberta_chinese_clue_tnews',               # TNEWS 主题分类\n",
    "    'hfl/chinese-bert-wwm-ext',                     # 若上面都失败则使用未特定微调模型\n",
    "]\n",
    "\n",
    "NUM_LABELS = None  \n",
    "USED_MODEL = None\n",
    "model = tokenizer = None\n",
    "for name in FINETUNED_CANDIDATES:\n",
    "    try:\n",
    "        print('尝试加载已微调模型:', name)\n",
    "        tokenizer = AutoTokenizer.from_pretrained(name)\n",
    "        model = AutoModelForSequenceClassification.from_pretrained(name)\n",
    "        USED_MODEL = name\n",
    "        print('[OK] 使用模型:', name)\n",
    "        break\n",
    "    except Exception as e:\n",
    "        print('[Fail]', name, '->', e)\n",
    "\n",
    "if model is None:\n",
    "    raise SystemExit('未能加载任何已微调模型, 请检查网络或改为离线加载。')\n",
    "\n",
    "model.set_train(False)\n",
    "print('标签数:', getattr(model, 'num_labels', '未知'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1a0511c",
   "metadata": {},
   "source": [
    "## 3. 推理示例\n",
    "\n",
    "模型为已微调成品分类器：\n",
    "1. 准备待分类文本列表。\n",
    "2. tokenizer 编码 (padding / truncation)。\n",
    "3. 前向 -> logits -> softmax -> argmax 得到标签。\n",
    "\n",
    "下一个代码单元给出批量推理示例与辅助函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "74dcd526",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "标签映射大小: 7\n",
      "苹果公司发布新款智能手机，市场反应热烈 -> financial news 0.8379\n",
      "国家出台教育改革政策，家长学生广泛关注 -> mainland China politics 0.8267\n",
      "昨晚的足球比赛十分激烈，球迷情绪高涨 -> sports 0.9990\n"
     ]
    }
   ],
   "source": [
    "import numpy as np, mindspore as ms\n",
    "from mindspore import Tensor, ops\n",
    "\n",
    "MAX_LEN = 128\n",
    "\n",
    "# 若模型自带 id2label 属性使用之, 否则构造占位标签映射\n",
    "def build_label_maps(m):\n",
    "    id2label = getattr(m.config, 'id2label', None) if hasattr(m, 'config') else None\n",
    "    label2id = getattr(m.config, 'label2id', None) if hasattr(m, 'config') else None\n",
    "    if id2label and isinstance(id2label, dict):\n",
    "        # 规范化 key 为 int\n",
    "        fixed = {}\n",
    "        for k,v in id2label.items():\n",
    "            try:\n",
    "                fixed[int(k)] = v\n",
    "            except: pass\n",
    "        id2label = fixed\n",
    "    else:\n",
    "        n = getattr(m, 'num_labels', None) or 0\n",
    "        id2label = {i: f'LABEL_{i}' for i in range(n)}\n",
    "    if not label2id:\n",
    "        label2id = {v:k for k,v in id2label.items()}\n",
    "    return id2label, label2id\n",
    "\n",
    "id2label, label2id = build_label_maps(model)\n",
    "print('标签映射大小:', len(id2label))\n",
    "\n",
    "def infer_texts(text_list):\n",
    "    enc = tokenizer(text_list, truncation=True, padding='max_length', max_length=MAX_LEN, return_tensors=None)\n",
    "    ids_np = np.array(enc['input_ids'], dtype=np.int32)\n",
    "    attn_np = np.array(enc['attention_mask'], dtype=np.int32)\n",
    "    tok_np  = np.zeros_like(ids_np, dtype=np.int32)\n",
    "    ids = Tensor(ids_np); attn = Tensor(attn_np); tok = Tensor(tok_np)\n",
    "    model.set_train(False)\n",
    "    out = model(input_ids=ids, attention_mask=attn, token_type_ids=tok)\n",
    "    probs = ops.softmax(out.logits, axis=-1).asnumpy()\n",
    "    preds = probs.argmax(-1)\n",
    "    results = []\n",
    "    for i, p in enumerate(preds):\n",
    "        label = id2label.get(int(p), str(p))\n",
    "        conf = float(probs[i][p])\n",
    "        results.append((text_list[i], label, conf))\n",
    "    return results\n",
    "\n",
    "# 示例\n",
    "sample_texts = [\n",
    "    '苹果公司发布新款智能手机，市场反应热烈',\n",
    "    '国家出台教育改革政策，家长学生广泛关注',\n",
    "    '昨晚的足球比赛十分激烈，球迷情绪高涨'\n",
    "]\n",
    "for t, lbl, conf in infer_texts(sample_texts):\n",
    "    print(t[:24], '->', lbl, f'{conf:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b508a253",
   "metadata": {},
   "source": [
    "## 4. 推理性能评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "75e66073",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "苹果公司发布最新芯片，引发科技股关注 -> financial news\n",
      "国家出台新的教育政策，家长和学生高度 -> mainland China politics\n",
      "国际足联公布最新排名，球队表现引热议 -> sports\n",
      "新赛季篮球联赛即将开始，球迷购票火爆 -> sports\n",
      "大型游戏公司发布新作预告，引玩家期待 -> financial news\n",
      "单句平均耗时: 85.69 ms (基于 30 次)\n",
      "批量(5)平均耗时: 103.42 ms -> 20.68 ms/样本\n"
     ]
    }
   ],
   "source": [
    "import time, numpy as np, mindspore as ms\n",
    "from mindspore import Tensor, ops\n",
    "\n",
    "perf_texts = [\n",
    "    '苹果公司发布最新芯片，引发科技股关注',\n",
    "    '国家出台新的教育政策，家长和学生高度关注',\n",
    "    '国际足联公布最新排名，球队表现引热议',\n",
    "    '新赛季篮球联赛即将开始，球迷购票火爆',\n",
    "    '大型游戏公司发布新作预告，引玩家期待'\n",
    "]\n",
    "enc = tokenizer(perf_texts, truncation=True, padding='max_length', max_length=MAX_LEN, return_tensors=None)\n",
    "ids = Tensor(np.array(enc['input_ids'], dtype=np.int32))\n",
    "attn = Tensor(np.array(enc['attention_mask'], dtype=np.int32))\n",
    "zero_tok = ops.zeros_like(ids)\n",
    "model.set_train(False)\n",
    "\n",
    "# 单句推理耗时\n",
    "def time_single(n=30, warmup=5):\n",
    "    single_ids = ids[0:1]; single_attn = attn[0:1]; single_tok = zero_tok[0:1]\n",
    "    for _ in range(warmup): _ = model(input_ids=single_ids, attention_mask=single_attn, token_type_ids=single_tok)\n",
    "    t0 = time.time()\n",
    "    for _ in range(n): _ = model(input_ids=single_ids, attention_mask=single_attn, token_type_ids=single_tok)\n",
    "    avg = (time.time() - t0) / n * 1000\n",
    "    print(f'单句平均耗时: {avg:.2f} ms (基于 {n} 次)')\n",
    "\n",
    "# 批量推理耗时\n",
    "def time_batch(n=50, warmup=5):\n",
    "    for _ in range(warmup): _ = model(input_ids=ids, attention_mask=attn, token_type_ids=zero_tok)\n",
    "    t0 = time.time()\n",
    "    for _ in range(n): _ = model(input_ids=ids, attention_mask=attn, token_type_ids=zero_tok)\n",
    "    avg = (time.time() - t0) / n * 1000\n",
    "    per_sample = avg / len(perf_texts)\n",
    "    print(f'批量({len(perf_texts)})平均耗时: {avg:.2f} ms -> {per_sample:.2f} ms/样本')\n",
    "\n",
    "out = model(input_ids=ids, attention_mask=attn, token_type_ids=zero_tok)\n",
    "probs = ops.softmax(out.logits, -1).asnumpy(); preds = probs.argmax(-1)\n",
    "for t, p in zip(perf_texts, preds):\n",
    "    label = id2label.get(int(p), str(p))\n",
    "    print(t[:18], '->', label)\n",
    "try:\n",
    "    time_single(); time_batch()\n",
    "except Exception as e:\n",
    "    print('性能计时失败:', e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7ed9239",
   "metadata": {},
   "source": [
    "## 5. 生成独立推理脚本与运行指令"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "355262e3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "已生成 inference_transformer_server.py (默认模型: uer/roberta-base-finetuned-chinanews-chinese)\n",
      "运行示例:\n",
      "  python inference_transformer_server.py 0.0.0.0 7860\n",
      "  python inference_transformer_server.py 0.0.0.0 7860 clue/roberta_chinese_clue_tnews\n",
      "  python inference_transformer_server.py 0.0.0.0 7860 --max-len 256\n",
      "  python inference_transformer_server.py 0.0.0.0 7860 --label-file export/label_mapping.json\n"
     ]
    }
   ],
   "source": [
    "# 运行本单元生成 inference_transformer_server.py\n",
    "# 用法: python inference_transformer_server.py <host> <port> [model_name] [--max-len 128] [--label-file export/label_mapping.json]\n",
    "\n",
    "# 直接设定默认模型（用户未指定时使用）；若运行时传入其他模型名则覆盖\n",
    "MODEL_PLACEHOLDER = 'uer/roberta-base-finetuned-chinanews-chinese'\n",
    "MAXLEN_PLACEHOLDER = MAX_LEN\n",
    "\n",
    "script_template = r'''#!/usr/bin/env python3\n",
    "# -*- coding: utf-8 -*-\n",
    "\"\"\"Transformer 中文文本分类 Gradio 服务 (MindSpore + MindSpore NLP)\n",
    "\n",
    "用法:\n",
    "  python inference_transformer_server.py <host> <port> [model_name] [--max-len 128] [--label-file export/label_mapping.json]\n",
    "示例:\n",
    "  python inference_transformer_server.py 0.0.0.0 7860\n",
    "  python inference_transformer_server.py 0.0.0.0 7860 uer/roberta-base-finetuned-chinanews-chinese\n",
    "  python inference_transformer_server.py 0.0.0.0 7860 --max-len 256\n",
    "  python inference_transformer_server.py 0.0.0.0 7860 --label-file export/label_mapping.json\n",
    "\n",
    "特点:\n",
    "  - 参考原 TextCNN server.py 的结构与输出样式\n",
    "  - 自动从 label 文件或模型 config 回退获取标签\n",
    "  - 仅文本显示 Top5 (去除图表组件，便于简化显示)\n",
    "  - Ascend 设备优先 (可根据实际环境自动回退)\n",
    "\"\"\"\n",
    "import sys, os, json, time\n",
    "import numpy as np\n",
    "import mindspore as ms\n",
    "from mindspore import Tensor\n",
    "from mindnlp.transformers import AutoTokenizer, AutoModelForSequenceClassification\n",
    "\n",
    "from argparse import ArgumentParser\n",
    "parser = ArgumentParser()\n",
    "parser.add_argument('host', help='监听地址，如 0.0.0.0')\n",
    "parser.add_argument('port', type=int, help='端口，如 7860')\n",
    "parser.add_argument('model_name', nargs='?', default='__MODEL_NAME__', help='HuggingFace 模型名称或本地目录')\n",
    "parser.add_argument('--max-len', type=int, default=__MAX_LEN__, help='Tokenizer 截断/填充长度')\n",
    "parser.add_argument('--label-file', default='export/label_mapping.json', help='标签映射 JSON (可选)')\n",
    "parser.add_argument('--device', default='Ascend', choices=['Ascend','CPU','GPU'], help='设备优先级 (默认 Ascend)')\n",
    "args = parser.parse_args()\n",
    "\n",
    "HOST, PORT = args.host, args.port\n",
    "MODEL_NAME = args.model_name\n",
    "MAX_LEN = args.max_len\n",
    "LABEL_FILE = args.label_file\n",
    "DEVICE = args.device\n",
    "\n",
    "# 设备设置\n",
    "try:\n",
    "    ms.set_context(device_target=DEVICE)\n",
    "    print(f\"[Context] device_target={ms.get_context('device_target')}\")\n",
    "except Exception as e:\n",
    "    print('[Context][WARN] 设置指定设备失败, 尝试回退 CPU:', e)\n",
    "    try:\n",
    "        ms.set_context(device_target='CPU')\n",
    "    except Exception as e2:\n",
    "        print('[Context][FATAL] 无法设置任何设备:', e2)\n",
    "        sys.exit(1)\n",
    "\n",
    "# 标签加载\n",
    "def load_id2label(model_obj):\n",
    "    if LABEL_FILE and os.path.exists(LABEL_FILE):\n",
    "        try:\n",
    "            with open(LABEL_FILE,'r',encoding='utf-8') as f:\n",
    "                data = json.load(f)\n",
    "            raw = data.get('id2label') or {}\n",
    "            id2label = {int(k):v for k,v in raw.items()}\n",
    "            if id2label:\n",
    "                print(f\"[Label] 从文件加载: {LABEL_FILE} (共 {len(id2label)})\")\n",
    "                return id2label\n",
    "            else:\n",
    "                print('[Label][WARN] 文件格式不符或为空, 回退模型 config')\n",
    "        except Exception as e:\n",
    "            print('[Label][WARN] 文件读取失败, 回退模型 config:', e)\n",
    "    cfg_map = getattr(getattr(model_obj,'config', None), 'id2label', None)\n",
    "    if cfg_map and isinstance(cfg_map, dict) and cfg_map:\n",
    "        try:\n",
    "            id2label = {int(k):v for k,v in cfg_map.items()}\n",
    "            print(f\"[Label] 使用模型 config id2label (共 {len(id2label)})\")\n",
    "            return id2label\n",
    "        except: pass\n",
    "    n = getattr(model_obj,'num_labels', 0)\n",
    "    id2label = {i: f'LABEL_{i}' for i in range(n)}\n",
    "    print(f\"[Label] 构造占位标签 (num_labels={n})\")\n",
    "    return id2label\n",
    "\n",
    "print(f\"[Init] 加载模型: {MODEL_NAME}\")\n",
    "try:\n",
    "    tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n",
    "    model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME)\n",
    "except Exception as e:\n",
    "    print('[Init][FATAL] 模型加载失败:', e)\n",
    "    sys.exit(1)\n",
    "model.set_train(False)\n",
    "id2label = load_id2label(model)\n",
    "\n",
    "if LABEL_FILE and not os.path.exists(LABEL_FILE):\n",
    "    try:\n",
    "        os.makedirs(os.path.dirname(LABEL_FILE) or '.', exist_ok=True)\n",
    "        with open(LABEL_FILE,'w',encoding='utf-8') as f:\n",
    "            json.dump({'id2label': {str(i):lab for i,lab in id2label.items()}, 'label2id': {lab:i for i,lab in id2label.items()}}, f, ensure_ascii=False, indent=2)\n",
    "        print(f\"[Label] 已写出标签映射: {LABEL_FILE}\")\n",
    "    except Exception as e:\n",
    "        print('[Label][WARN] 标签写出失败:', e)\n",
    "\n",
    "import mindspore.ops as ops\n",
    "\n",
    "def classify_text(text: str):\n",
    "    if not text.strip():\n",
    "        return '**提示**: 请输入文本'\n",
    "    enc = tokenizer([text], truncation=True, padding='max_length', max_length=MAX_LEN, return_tensors=None)\n",
    "    ids = Tensor(np.array(enc['input_ids'], dtype=np.int32))\n",
    "    attn = Tensor(np.array(enc['attention_mask'], dtype=np.int32))\n",
    "    toks = Tensor(np.zeros_like(np.array(enc['input_ids'], dtype=np.int32)))\n",
    "    out = model(input_ids=ids, attention_mask=attn, token_type_ids=toks)\n",
    "    probs = ops.softmax(out.logits, axis=-1).asnumpy()[0]\n",
    "    pred = int(probs.argmax())\n",
    "    best_label = id2label.get(pred, str(pred))\n",
    "    best_conf = float(probs[pred])\n",
    "    ranking = [(id2label.get(i,str(i)), float(probs[i])) for i in range(len(probs))]\n",
    "    ranking.sort(key=lambda x: x[1], reverse=True)\n",
    "    lines = [f'**预测类别**: {best_label}', f'**置信度**: {best_conf:.4f}', '', '**Top5 概率:**']\n",
    "    for i,(lab,pv) in enumerate(ranking[:5]):\n",
    "        lines.append(f'{i+1}. {lab}: {pv:.4f}')\n",
    "    return '\\n'.join(lines)\n",
    "\n",
    "print('[Gradio] 构建界面...')\n",
    "try:\n",
    "    import gradio as gr\n",
    "except ImportError:\n",
    "    print('[Gradio] 未安装 gradio, 正在尝试安装...')\n",
    "    os.system('pip install gradio')\n",
    "    import gradio as gr\n",
    "\n",
    "examples = [\n",
    "    '苹果公司发布新品手机，市场反应强烈',\n",
    "    '国家队在世界杯预选赛中取得关键胜利',\n",
    "    '人工智能技术在医疗影像诊断领域突破'\n",
    "]\n",
    "\n",
    "with gr.Blocks(title='Transformer 文本分类器') as demo:\n",
    "    gr.Markdown('# Transformer 中文文本分类')\n",
    "    gr.Markdown(f'模型: {MODEL_NAME} | 标签数: {len(id2label)} | MaxLen: {MAX_LEN}')\n",
    "    with gr.Row():\n",
    "        with gr.Column(scale=2):\n",
    "            inp = gr.Textbox(label='输入文本', lines=5, placeholder='输入需要分类的中文文本...')\n",
    "            btn = gr.Button('开始分类', variant='primary')\n",
    "            gr.Examples(examples=examples, inputs=inp, label='示例')\n",
    "        with gr.Column(scale=2):\n",
    "            out_md = gr.Markdown()\n",
    "    btn.click(classify_text, inputs=inp, outputs=out_md)\n",
    "    gr.Markdown('---')\n",
    "    gr.Markdown('推理由 MindSpore + MindNLP 驱动，界面基于 Gradio。')\n",
    "\n",
    "print(f'[Gradio] 启动: http://{HOST}:{PORT}')\n",
    "try:\n",
    "    demo.launch(server_name=HOST, server_port=PORT, share=False, show_error=True)\n",
    "except Exception as e:\n",
    "    print('[Gradio][FATAL] 启动失败:', e)\n",
    "    sys.exit(1)\n",
    "'''\n",
    "\n",
    "script_content = script_template.replace('__MODEL_NAME__', MODEL_PLACEHOLDER).replace('__MAX_LEN__', str(MAXLEN_PLACEHOLDER))\n",
    "\n",
    "out_file = 'inference_transformer_server.py'\n",
    "with open(out_file,'w',encoding='utf-8') as f:\n",
    "    f.write(script_content)\n",
    "\n",
    "print(f'已生成 {out_file} (默认模型: {MODEL_PLACEHOLDER})')\n",
    "print('运行示例:')\n",
    "print('  python inference_transformer_server.py 0.0.0.0 7860')\n",
    "print('  python inference_transformer_server.py 0.0.0.0 7860 clue/roberta_chinese_clue_tnews')\n",
    "print('  python inference_transformer_server.py 0.0.0.0 7860 --max-len 256')\n",
    "print('  python inference_transformer_server.py 0.0.0.0 7860 --label-file export/label_mapping.json')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e917d15-f492-439a-99f3-7e15c5f40115",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
